package de.seminar.lego3D;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

/**
 * Class that handles all communication between this client application and the server.
 * @author Christian Lander
 *
 */
public class ClientConnector {

	/**
	 * port for the communication
	 */
	private final int PORT = 9876;

	/**
	 * clinetSocket
	 */
	private DatagramSocket clientSocket;

	/**
	 * Flag that indicates the state of the connection.
	 */
	private boolean connected = false;

	/**
	 * Separator for the messages
	 */
	private final String seperator = "#";

	/**
	 * IP adress from the server
	 */
	private InetAddress remoteAdress;

	/**
	 * context of the main activity
	 */
	private Context context;

	/**
	 * receiver to receive messages over the socket
	 */
	private Receiver receiver;

	/**
	 * handler to send a callback message to the main activity if the connection
	 * to the server is established
	 */
	private Handler communicationHandler;

	/**
	 * Creates a new client connector.
	 * @param ctx - context of the main activity
	 * @param handler - handler to send callbacks to the main activity
	 */
	public ClientConnector(Context ctx, Handler handler) {
		context = ctx;
		communicationHandler = handler;
	}

	/**
	 * sends a datagram packet to the server
	 * 
	 * @param p - the datagram packet to send
	 */
	private synchronized void sendPacket(DatagramPacket p) {
		Log.v("client", "attemp to send message: " + p.toString());
		int tries = 0;
		while (tries < 3) {
			try {
				Log.v("send", "try*************************");
				clientSocket.send(p);
				Log.v("send", "successfull*************************");
				tries = 3;
			} catch (IOException e) {
				tries++;
				if (tries == 3) {
					e.printStackTrace();
					Toast.makeText(context,
							"Could not send message: Not connected",
							Toast.LENGTH_SHORT).show();
					final Bundle bundle = new Bundle();
					bundle.putBoolean(Constants.SUCCESSFUL, false);
					Message msg = new Message();
					msg.setData(bundle);
					communicationHandler.sendMessage(msg);
				}
			}
		}

	}

	/**
	 * starts a connection to the socket, by opening the input and output stream
	 * 
	 * @param IP
	 *            - the IP address of the server
	 */
	synchronized private void startConnection(String IP) {
		try {
			Log.v("LEGO 3D App", "start to connect");

			remoteAdress = InetAddress.getByName(IP);
			this.clientSocket = new DatagramSocket(PORT);

		//	connected = true;
			Log.v("LEGO 3D App", "connected");
		} catch (SocketException e) {
			Toast.makeText(context, "Cannot establish a connection",
					Toast.LENGTH_LONG).show();
			e.printStackTrace();
		} catch (UnknownHostException e) {
			Toast.makeText(context, "Cannot establish a connection",
					Toast.LENGTH_LONG).show();
			e.printStackTrace();
		}
	}

	/**
	 * Causes the client to send a connection message
	 */
	public void sendConnect() {
		if (receiver == null) {
			receiver = new Receiver();
			receiver.startListening();
			receiver.start();
		} else {
			if (!receiver.isListening()) {
				receiver.startListening();
			}
		}

		byte[] sendData = new byte[1024];
		String msg = "CONNECT";

		Log.v("send connect", "send message: " + msg);

		sendData = msg.getBytes();
		DatagramPacket packet = new DatagramPacket(sendData, sendData.length,
				remoteAdress, PORT);
		sendPacket(packet);
	}

	/**
	 * Sends a new message with the lego brick information to the server.
	 * 
	 * @param currentBrick
	 *            - the lego brick information to send
	 */
	public void sendBirckInformation(Legobrick currentBrick) {
		if (connected) {
			byte[] sendData = new byte[1024];
			String msg = "NEW-BRICK" + seperator + currentBrick.layoutToString() + currentBrick.color;

			Log.v("send new brick", "send message: " + msg);

			sendData = msg.getBytes();
			DatagramPacket packet = new DatagramPacket(sendData,
					sendData.length, remoteAdress, PORT);
			sendPacket(packet);
		} else {
			Toast.makeText(
					context,
					"Cannot send, because the application \n is not connected to the server.",
					Toast.LENGTH_SHORT).show();
		}
	}

	/**
	 * Tells the server, that we disconnect
	 */
	public boolean sendDisconnect() {
		if (connected) {
			byte[] sendData = new byte[1024];
			String msg = "DISCONNECT";
			sendData = msg.getBytes();
			DatagramPacket packet = new DatagramPacket(sendData,
					sendData.length, remoteAdress, PORT);
			sendPacket(packet);

			return true;
		} else {
			return false;
		}
	}

	/**
	 * kills the connection by closing the socket and stopping the receiver.
	 */
	public void kill() {
		killReceiver();
		if (clientSocket != null && !clientSocket.isConnected()) {
			clientSocket.close();
			connected = !clientSocket.isClosed() || clientSocket.isConnected();
			clientSocket = null;
		}
	}

	/**
	 * Starts a connection to the remote Host, and sends a CONNECT message to
	 * identify on the server.
	 * 
	 * @param IP
	 *            - the IP address of the server
	 */
	public void start(String IP) {
		startConnection(IP);
		sendConnect();
	}

	/**
	 * Kills the receiver by stopping the thread.
	 */
	public void killReceiver() {
		if (receiver != null) {
			receiver.stopListening();
			if (!receiver.isInterrupted()) {
				receiver.interrupt();
			}
			receiver = null;
		}
	}

	/**
	 * Returns true if the client is connected to the server. Otherwise returns
	 * false.
	 * 
	 * @return
	 */
	public boolean connected() {
		return connected;
	}

	/**
	 * Receiver class, to receive messages over socket.
	 * 
	 * @author christian
	 * 
	 */
	private class Receiver extends Thread {

		/**
		 * flag that indicates if we are listening
		 */
		private boolean listening = true;

		@Override
		public void run() {
			while (!this.isInterrupted()) {
				if (listening) {
					try {
						Log.v("receiver", "start listening");
						byte[] receiveData = new byte[1024];
						DatagramPacket receivePacket = new DatagramPacket(
								receiveData, receiveData.length, remoteAdress,
								PORT);
						clientSocket.receive(receivePacket);

						String receivedRAW = new String(receivePacket.getData());
						Log.v("receiver", "received: " + receivedRAW);

						String received = receivedRAW.trim();

						// parsing the string and tells the user that the
						// connection is established
						String[] data = received.split(seperator);
						if (data[0].equalsIgnoreCase("CONNECTED")) {
							connected = true;
							final Bundle bundle = new Bundle();
							bundle.putBoolean(Constants.SUCCESSFUL, true);
							Message msg = new Message();
							msg.setData(bundle);
							communicationHandler.sendMessage(msg);
						}
					} catch (IOException e) {
						e.printStackTrace();
						Log.v("receiver", "IO exc");
					}
				}
			}
		}

		/**
		 * stops listening to the display
		 */
		public synchronized void stopListening() {
			this.listening = false;
		}

		/**
		 * starts listening to the display
		 */
		public synchronized void startListening() {
			this.listening = true;
		}

		/**
		 * Returns true if the receiver thread is still running, otherwise false.
		 * @return
		 */
		public synchronized boolean isListening() {
			return this.listening;
		}
	}
}
